package org.lilred.nzbberry.queue;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.lilred.nzbberry.observer.Observable;
import org.lilred.nzbberry.observer.Observer;
import org.lilred.nzbberry.webservice.SabnzbdActionClient;

/**
 * Provides an interface to the SABnzbd API
 * @author Jacob Tabak
 * @version 1.0
 */
public class QueueProxy implements Observable {
	//Fields set by SABnzbd
	private int _cache_limit;
	private String[] _categories;
	private String[] _scripts;
	private boolean _paused;
	private String _new_rel_url;
	private boolean _restart_req;
	private int _slots;
	private boolean _isverbose;
	private int _start;
	private String _version;
	private double _diskspacetotal2;
	private String _color_scheme;
	private boolean _darwin;
	private boolean _nt;
	private String _status;
	private String _last_warning;
	private int _have_warnings;
	private int _cache_art;
	private String _finishaction;
	private int _noofslots;
	private int _cache_size;
	private int _finish;
	private String _new_release;
	private int _pause_int;
	private double _mbleft;
	private double _diskspace2;
	private double _diskspace1;
	private double _diskspacetotal1;
	private String _timeleft;
	private double _mb;
	private boolean _newzbinDetails;
	private String _eta;
	private String _nzb_quota;
	private String _loadavg;
	private int _limit;
	private double _kbpersec;
	private int _speedlimit;
	private String _webdir;
	private int _queue_details;
	
	//Internal fields
	private static QueueProxy _instance;
	private Hashtable _nzos = new Hashtable();
	private Vector _observers = new Vector();
	
	public static QueueProxy getInstance() {
		if (_instance == null) _instance = new QueueProxy();
		return _instance;
	}
	
	private QueueProxy() { };
	
	private void add(AddMode mode, String name, String niceName, PostProcessing postProcessing, String script, String category, Priority priority) {
		StringBuffer queryString = new StringBuffer();
		queryString.append("&mode=" + mode);
		queryString.append("&name=" + name);
		if (mode != AddMode.NEWZBIN_ID) queryString.append("&nzbname=" + niceName);
		if (postProcessing != null) queryString.append("&pp=" + postProcessing);
		if (scriptExists(script)) queryString.append("&script=" + script);
		if (categoryExists(category)) queryString.append("&category=" + category);		
		if (priority != null) queryString.append("&priority=" + priority);
		new SabnzbdActionClient().performAction(queryString.toString());
	}

	/**
	 * Adds an NZB by URL.  Obviously will not work for sites that require a session.
	 * @param url The URL of the remote file.
	 * @param niceName Used for the final folder name, and input for any post-processing sorting.  Can be null.
	 * @param postProcessing The post processing options for this NZB. Use PostProcessing.DEFAULT for default.
	 * @param script The script to be executed after this NZB has finished processing.  Can be null.
	 * @param category The category of the NZB.  Can be null.
	 * @param priority The priority of the NZB.  Use Priority.DEFAULT for default.
	 */
	public void addByUrl(String url, String niceName, PostProcessing postProcessing, String script, String category, Priority priority) {
		add(AddMode.URL, url, niceName, postProcessing, script, category, priority);
	}

	/**
	 * Adds an NZB by Newzbin ID.
	 * @param newzbinId The Newzbin ID of the NZB
	 * @param postProcessing The post processing options for this NZB. Use PostProcessing.DEFAULT for default.
	 * @param script The script to be executed after this NZB has finished processing.  Can be null.
	 * @param category The category of the NZB.  Can be null.
	 * @param priority The priority of the NZB.  Use Priority.DEFAULT for default.
	 */
	public void addByNewzbinId(String newzbinId, PostProcessing postProcessing, String script, String category, Priority priority) {
		add(AddMode.NEWZBIN_ID, newzbinId, null, postProcessing, script, category, priority);
	}

	/**
	 * Adds an NZB by file upload
	 * @param data The NZB represented by a byte array
	 * @param niceName Used for the final folder name, and input for any post-processing sorting.  Can be null.
	 * @param postProcessing The post processing options for this NZB. Use PostProcessing.DEFAULT for default.
	 * @param script The script to be executed after this NZB has finished processing.  Can be null.
	 * @param category The category of the NZB.  Can be null.
	 * @param priority The priority of the NZB.  Use Priority.DEFAULT for default.
	 */
	public void addByFileUpload(byte[] data, String niceName, PostProcessing postProcessing, String script, String category, Priority priority) {
		//TODO:
	}

	/**
	 * Adds an NZB by local path.  Only works for remote files already on the server.
	 * @param filePath the path of the NZB
	 * @param niceName Used for the final folder name, and input for any post-processing sorting.  Can be null.
	 * @param postProcessing The post processing options for this NZB. Use PostProcessing.DEFAULT for default.  
	 * @param script The script to be executed after this NZB has finished processing.  Can be null. 
	 * @param category The category of the NZB.  Can be null.
	 * @param priority The priority of the NZB.  Use Priority.DEFAULT for default.
	 * @see PostProcessing
	 * @see Priority
	 */
	public void addByPath(String filePath, String niceName, PostProcessing postProcessing, String script, String category, Priority priority) {
		add(AddMode.FILE_PATH, filePath, niceName, postProcessing, script, category, priority);
	}

	/**
	 * @param category
	 * @return whether or not the category exists on the SABnzbd server
	 */
	public boolean categoryExists(String category) {
		if (category == null) return false;
		for (int i = 0; i < _categories.length; i++) {
			if (_categories[i].equals(category)) return true;
		}
		return false;
	}

	/**
	 * Delete all queue items
	 */
	public void delete() {
		new SabnzbdActionClient().performAction("mode=queue&name=delete&value=all");
	}

	/**
	 * Delete multiple queue items
	 * @param nzoIds the array of nzoIds of queue items to delete
	 */
	public void delete(String[] nzoIds) {
		StringBuffer queryString = new StringBuffer();
		queryString.append("&mode=queue&name=delete&value=");
		for (int i = 0; i < nzoIds.length; i++) {
			queryString.append(nzoIds[i]);
			//append commas to all but the last element
			if (i != nzoIds.length - 1) queryString.append(',');
		}
		new SabnzbdActionClient().performAction("mode=queue&name=delete&value=" + queryString.toString());
	}

	/**
	 * Delete one queue item
	 * @param nzoId the nzoId of the queue item to delete
	 */
	public void delete(String nzoId) {
		new SabnzbdActionClient().performAction("mode=queue&name=delete&value=" + nzoId);
	}

	/**
	 * @return whether or not the nzo has Newzbin Details
	 */
	public boolean hasNewzbinDetails() {
		return _newzbinDetails;
	}

	/**
	 * @return whether or not the queue is globally paused
	 */
	public boolean isPaused() {
		return _paused;
	}

	/**
	 * @return whether or not a restart is required for settings to take effect
	 */
	public boolean isRestartReq() {
		return _restart_req;
	}

	/**
	 * @return the verbosity
	 */
	public boolean isVerbose() {
		return _isverbose;
	}

	/**
	 * @return whether or not the server is darwin
	 */
	public boolean isDarwin() {
		return _darwin;
	}

	/**
	 * @return whether or not the server is nt
	 */
	public boolean isNt() {
		return _nt;
	}

	public int getCacheArt() {
		return _cache_art;
		//TODO: determine what this does and document it
	}

	/**
	 * @return the cache limit
	 */
	public int getCacheLimit() {
		return _cache_limit;
	}

	/**
	 * @return the categories
	 */
	public String[] getCategories() {
		return _categories;
	}

	/**
	 * @return the color scheme
	 */
	public String getColorScheme() {
		return _color_scheme;
	}

	/**
	 * @return the finish action
	 */
	public String getFinishAction() {
		return _finishaction;
	}

	/**
	 * @return the cache size
	 */
	public int getCacheSize() {
		return _cache_size;
	}

	/**
	 * @return the finish
	 */
	public int getFinish() {
		return _finish;
	}

	/**
	 * @return the disk space available on the second disk
	 */
	public double getDiskSpace1() {
		return _diskspace1;
	}

	/**
	 * @return the disk space available on the first disk
	 */
	public double getDiskSpace2() {
		return _diskspace2;
	}

	/**
	 * @return the total disk space available
	 */
	public double getDiskSpaceTotal() {
		return _diskspacetotal1;
	}

	/**
	 * @return the diskspacetotal2
	 */
	public double getDiskSpaceTotal2() {
		return _diskspacetotal2;
	}

	/**
	 * @return the eta in string format
	 */
	public String getEta() {
		return _eta;
	}

	/**
	 * @return the current kbps
	 */
	public double getKbPerSec() {
		return _kbpersec;
	}

	/**
	 * @return the most recent warning
	 */
	public String getLastWarning() {
		return _last_warning;
	}

	/**
	 * @return the limit of ???
	 */
	public int getLimit() {
		return _limit;
	}

	/**
	 * @return the load average
	 */
	public String getLoadAvg() {
		return _loadavg;
	}

	/**
	 * @return the total size of the queue in megabytes
	 */
	public double getMb() {
		return _mb;
	}

	/**
	 * @return the megabytes left
	 */
	public double getMbLeft() {
		return _mbleft;
	}

	/**
	 * @return the new release
	 */
	public String getNewRelease() {
		return _new_release;
	}

	/**
	 * @return the url for the new release
	 */
	public String getNewRelUrl() {
		return _new_rel_url;
	}

	/**
	 * @return the number of slots
	 */
	public int getNumSlots() {
		return _noofslots;
	}

	public NZBObject getNzoById(String nzoId) {
		if (nzoExists(nzoId)) return (NZBObject) _nzos.get(nzoId);
		else return null;
	}

	public NZBObject getNzoByIndex(int index) {
		if (index < getNumSlots()) {
			Enumeration e = getNzoIds();
			while (e.hasMoreElements()) {
				Object key = e.nextElement();
				NZBObject currentNzo = (NZBObject) _nzos.get(key);
				if (currentNzo.getIndex() == index) return currentNzo;
			}
		}
		return null;
	}

	/**
	 * @return the nzb quota
	 */
	public String getNzbQuota() {
		return _nzb_quota;
	}

	/**
	 * @return the pause int
	 */
	public int getPauseInt() {
		return _pause_int;
	}

	/**
	 * @return the queue details
	 */
	public int getQueueDetails() {
		return _queue_details;
	}

	public void getQueueItem() {
		//TODO
	}

	/**
	 * @return the script names from SABnzbd
	 */
	public String[] getScripts() {
		return _scripts;
	}

	public Enumeration getNzoIds() {
		return _nzos.keys();
	}
	
	/**
	 * @return the number of slots
	 */
	public int getSlotCount() {
		return _slots;
	}

	/**
	 * @return the speed limit
	 */
	public int getSpeedLimit() {
		return _speedlimit;
	}

	/**
	 * @return the start
	 */
	public int getStart() {
		return _start;
	}

	/**
	 * @return the status
	 */
	public String getStatus() {
		return _status;
	}

	/**
	 * @return the estimated amount of time until all items in the queue are complete
	 */
	public String getTimeLeft() {
		return _timeleft;
	}

	/**
	 * @return the version
	 */
	public String getVersion() {
		return _version;
	}

	/**
	 * @return the number of warnings
	 */
	public int getWarningsCount() {
		return _have_warnings;
	}

	/**
	 * @return the web directory
	 */
	public String getWebDir() {
		return _webdir;
	}

	/**
	 * Swap the positions of two NZBs
	 * @param nzoId1 the nzoId of the first NZB
	 * @param nzoId2 the nzoId of the second NZB
	 * @return true if both nzoIds are valid, false otherwise
	 */
	public boolean moveSwap(String nzoId1, String nzoId2) {
		if (!nzoExists(nzoId1) || !nzoExists(nzoId2)) return false;
		new SabnzbdActionClient().performAction("&mode=switch&value=" + nzoId1 + "&value2=" + nzoId2);
		return true;
	}
	
	/**
	 * @param nzoId
	 * @return whether or not the nzoId exists in the queue
	 */
	public boolean nzoExists(String nzoId) {
		return (_nzos.get(nzoId) != null);
	}
	
	/**
	 * Move an NZB to a specific location in the queue
	 * @param nzoId the nzoId of the NZB to move
	 * @param index the destination index in the queue
	 * @return false if the nzb doesn't exist or the index is invalid
	 */
	public boolean moveToLocation(String nzoId, int index) {
		if (!nzoExists(nzoId) || index >= getNumSlots()) return false;
		new SabnzbdActionClient().performAction("&mode=switch&value=" + nzoId + "&value2=" + index);
		return true;
	}
	
	public void notifyObservers() {
		for (int i = 0; i < _observers.size(); i++) {
			((Observer) _observers.elementAt(i)).updateObserver(this, 0);
		}
	}
	
	/**
	 * Pause a specific NZB.
	 * @param nzoId the nzoId of the NZB to pause
	 * @return false if the nzoId is invalid or the NZB is already paused
	 */
	public boolean pauseOne(String nzoId) {
		if (!nzoExists(nzoId) || getNzoById(nzoId).getStatus().equals(SlotStatus.PAUSED)) return false;
		new SabnzbdActionClient().performAction("&mode=pause&value=" + nzoId);
		return true;
	}
	
	/**
	 * Pause SABnzbd
	 * @return false if SABnzbd already was paused
	 */
	public boolean pauseGlobal() {
		if (_status.equals(QueueStatus.PAUSED)) return false;
		new SabnzbdActionClient().performAction("&mode=pause");
		return true;
	}
	
	/**
	 * Pause SABnzbd for a specific amount of time
	 * @param duration in seconds of pause
	 */
	public void pauseGlobalTemporary(int duration) {
		//TODO Determine what happens if the queue is already paused and a temporary pause is called.
		new SabnzbdActionClient().performAction("&mode=config&name=set_pause&value=" + duration);
	}
	
	/**
	 * Register an observer to watch for updates to the queue
	 * @param the observer
	 */
	public void registerObserver(Observer o) {
		_observers.addElement(o);
	}

	/**
	 * Resume the downloading of one NZB
	 * @param nzoId of the NZB to resume
	 * @return false if the download wasn't paused
	 */
	public boolean resumeOne(String nzoId) {
		if (!nzoExists(nzoId)) return false;
		if (getNzoById(nzoId).getStatus().equals(SlotStatus.PAUSED)) return false;
		new SabnzbdActionClient().performAction("&mode=pause&value=" + nzoId);
		return true;
	}
	
	/**
	 * Unpause SABnzbd
	 * @return false if SABnzbd wasn't paused
	 */
	public boolean resumeGlobal() {
		if (!_status.equals(QueueStatus.PAUSED)) return false;
		new SabnzbdActionClient().performAction("&mode=resume");
		return true;
	}
	
	/**
	 * Determine whether or not a script exists
	 * @param script
	 * @return whether or not the script exists
	 */
	public boolean scriptExists(String script) {
		if (script == null) return false;
		for (int i = 0; i < _scripts.length; i++) {
			if (_scripts[i].equals(script)) return true;
		}
		return false;
	}

	/**
	 * Set the category of an NZB (used for post processing)
	 * @param nzoId the nzoId of the NZB
	 * @param category the destination category
	 * @return false if the nzoId was invalid or the category doesn't exist
	 */
	public boolean setCategory(String nzoId, String category) {
		if (!nzoExists(nzoId) || !categoryExists(category)) return false;
		new SabnzbdActionClient().performAction("&mode=change_cat&value=" + nzoId + "&value2=" + category);
		return true;
	}

	/**
	 * Specify the action to be performed when SABnzbd completes all downloads.
	 * This can be either be a script or one of the predefined actions.
	 * @see FinishAction
	 * @param action the action to perform
	 */
	public void setFinishAction(FinishAction action) {
		new SabnzbdActionClient().performAction("&mode=change_complete_action&value=" + action);
	}

	/**
	 * Set the friendly name of an NZB.  This will be used for the destination directory and postprocessing.
	 * @param nzoId the nzoId of the NZB
	 * @param newName the new name
	 * @return false if the nzoId is invalid
	 */
	public boolean setName(String nzoId, String newName) {
		if (!nzoExists(nzoId)) return false;
		new SabnzbdActionClient().performAction("&mode=queue&name=rename&value=" + nzoId + "&value2=" + newName);
		return true;
	}

	/**
	 * Set the post processing action for an NZB
	 * @param nzoId the nzoId of the NZB
	 * @param postProcessing the new post processing action
	 * @return false if the nzoId is invalid
	 * @see PostProcessing
	 */
	public boolean setPostProcessing(String nzoId, PostProcessing postProcessing) {
		if (!nzoExists(nzoId)) return false;
		new SabnzbdActionClient().performAction("&mode=change_opts&value=" + nzoId + "&value2=" + postProcessing);
		return true;
	}
	
	/**
	 * Set the priority of an NZB
	 * @param nzoId the nzoId of the NZB
	 * @param priority the new priority
	 * @return false if the nzoId is invalid
	 * @see Priority
	 */
	public boolean setPriority(String nzoId, Priority priority) {
		if (!nzoExists(nzoId)) return false;
		new SabnzbdActionClient().performAction("&mode=queue&name=priority&value=" + nzoId + "&value2=" + priority);
		return true;
	}
	
	/**
	 * Set the script to be executed after an NZB completes.
	 * @param nzoId the nzoId of the NZB
	 * @param script the new script
	 * @return false if the nzoId is invalid or the script doesn't exist
	 */
	public boolean setScript(String nzoId, String script) {
		if (!nzoExists(nzoId) || !scriptExists(script)) return false;
		new SabnzbdActionClient().performAction("&mode=change_script&value=" + nzoId + "&value2=" + script);
		return true;
	}

	/**
	 * Shut down SABnzbd
	 */
	public void shutDown() {
		new SabnzbdActionClient().performAction("&mode=shutdown");
	}

	/**
	 * Request updated queue data from SABnzbd.  All data within the object will be refreshed.
	 */
	public void updateQueue() {
		//TODO
	}
}
